<template>
    <div
        class="ui-slider"
        role="slider"

        :aria-valuemax="100"
        :aria-valuemin="0"
        :aria-valuenow="localValue"
        :class="classes"
        :tabindex="disabled ? null : 0"

        @blur="onBlur"
        @focus="onFocus"
        @keydown.down.prevent="decrementValue"
        @keydown.left.prevent="decrementValue"
        @keydown.right.prevent="incrementValue"
        @keydown.up.prevent="incrementValue"
    >
        <input
            class="ui-slider__hidden-input"
            type="hidden"

            :name="name"
            :value="value"

            v-if="name"
        >

        <div class="ui-slider__icon" v-if="hasIcon">
            <slot name="icon">
                <ui-icon :icon="icon"></ui-icon>
            </slot>
        </div>

        <div
            class="ui-slider__track"
            ref="track"
            @mousedown="onDragStart"
            @touchstart="onDragStart"
        >
            <div class="ui-slider__track-background">
                <span
                    class="ui-slider__snap-point"
                    :style="{ left: point + '%' }"

                    v-if="snapToSteps"
                    v-for="point in snapPoints"
                ></span>
            </div>

            <div class="ui-slider__track-fill" :style="fillStyle"></div>

            <div class="ui-slider__thumb" ref="thumb" :style="thumbStyle">
                <div class="ui-slider__marker" v-if="showMarker">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="36" height="36">
                        <path d="M11 .5c-1.7.2-3.4.9-4.7 2-1.1.9-2 2-2.5 3.2-1.2 2.4-1.2 5.1-.1 7.7 1.1 2.6 2.8 5 5.3 7.5 1.2 1.2 2.8 2.7 3 2.7 0 0 .3-.2.6-.5 3.2-2.7 5.6-5.6 7.1-8.5.8-1.5 1.1-2.6 1.3-3.8.2-1.4 0-2.9-.5-4.3-1.2-3.2-4.1-5.4-7.5-5.8-.5-.2-1.5-.2-2-.2z"/>
                    </svg>

                    <span class="ui-slider__marker-text">{{ markerText }}</span>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
import UiIcon from './UiIcon.vue';

import classlist from './helpers/classlist';
import RespondsToWindowResize from './mixins/RespondsToWindowResize.js';

export default {
    name: 'ui-slider',

    props: {
        name: String,
        icon: String,
        value: {
            type: Number,
            required: true
        },
        step: {
            type: Number,
            default: 10
        },
        snapToSteps: {
            type: Boolean,
            default: false
        },
        showMarker: {
            type: Boolean,
            default: false
        },
        markerValue: Number,
        disabled: {
            type: Boolean,
            default: false
        }
    },

    data() {
        return {
            initialValue: this.value,
            isActive: false,
            isDragging: false,
            thumbSize: 0,
            trackLength: 0,
            trackOffset: 0,
            localValue: this.value
        };
    },

    computed: {
        classes() {
            return [
                { 'is-dragging': this.isDragging },
                { 'is-disabled': this.disabled },
                { 'is-active': this.isActive },
                { 'has-icon': this.hasIcon },
                { 'has-marker': this.showMarker }
            ];
        },

        hasIcon() {
            return Boolean(this.$slots.icon) || Boolean(this.icon);
        },

        fillStyle() {
            return { transform: 'scaleX(' + (this.localValue / 100) + ')' };
        },

        thumbStyle() {
            return {
                transform: 'translateX(' + (
                    ((this.localValue / 100) * this.trackLength) - (this.thumbSize / 2)
                ) + 'px)'
            };
        },

        markerText() {
            return this.markerValue ? this.markerValue : this.value;
        },

        snapPoints() {
            const points = [];
            let index = 0;
            let point = index * this.step;

            while (point <= 100) {
                points.push(point);
                index++;
                point = index * this.step;
            }

            return points;
        }
    },

    watch: {
        value() {
            this.setValue(this.value);
        },

        isDragging() {
            const operation = this.isDragging ? 'add' : 'remove';
            classlist[operation](document.body, 'ui-slider--is-dragging');
        }
    },

    mounted() {
        this.initializeSlider();
    },

    beforeDestroy() {
        this.teardownSlider();
    },

    methods: {
        reset() {
            this.setValue(this.initialValue);
        },

        onFocus() {
            this.isActive = true;
            this.$emit('focus');
        },

        onBlur() {
            this.isActive = false;
            this.$emit('blur');
        },

        onExternalClick(e) {
            if (!this.$el.contains(e.target)) {
                this.onBlur();
            }
        },

        setValue(value) {
            if (value > 100) {
                value = 100;
            } else if (value < 0) {
                value = 0;
            }

            if (value === this.localValue) {
                return;
            }

            this.localValue = value;
            this.$emit('input', value);
            this.$emit('change', value);
        },

        incrementValue() {
            this.setValue(this.localValue + this.step);
        },

        decrementValue() {
            this.setValue(this.localValue - this.step);
        },

        getTrackOffset() {
            let el = this.$refs.track;
            let offset = el.offsetLeft;

            while (el.offsetParent) {
                el = el.offsetParent;
                offset += el.offsetLeft;
            }

            return offset;
        },

        getPointStyle(point) {
            return {
                left: point + '%'
            };
        },

        refreshSize() {
            this.thumbSize = this.$refs.thumb.offsetWidth;
            this.trackLength = this.$refs.track.offsetWidth;
            this.trackOffset = this.getTrackOffset(this.$refs.track);
        },

        initializeSlider() {
            document.addEventListener('touchend', this.onDragStop);
            document.addEventListener('mouseup', this.onDragStop);
            document.addEventListener('click', this.onExternalClick);

            this.$on('window-resize', () => {
                this.refreshSize();
                this.isDragging = false;
            });

            this.refreshSize();
            this.initializeDrag();
        },

        teardownSlider() {
            document.removeEventListener('touchend', this.onDragStop);
            document.removeEventListener('mouseup', this.onDragStop);
            document.removeEventListener('click', this.onExternalClick);
        },

        initializeDrag() {
            const value = this.getEdge(this.localValue ? this.localValue : 0, 0, 100);
            this.setValue(value);
        },

        onDragStart(e) {
            if (this.disabled) {
                return;
            }

            if (!this.isActive) {
                this.onFocus();
            }

            this.isDragging = true;
            this.dragUpdate(e);

            document.addEventListener('touchmove', this.onDragMove);
            document.addEventListener('mousemove', this.onDragMove);

            this.$emit('dragstart', this.localValue, e);
        },

        onDragMove(e) {
            this.dragUpdate(e);
        },

        dragUpdate(e) {
            const position = e.touches ? e.touches[0].pageX : e.pageX;
            const value = this.getEdge(
                ((position - this.trackOffset) / this.trackLength) * 100, 0, 100
            );

            if (this.isDragging) {
                this.setValue(Math.round(value));
            }
        },

        onDragStop(e) {
            this.isDragging = false;

            if (this.snapToSteps && this.value % this.step !== 0) {
                this.setValue(this.getNearestSnapPoint());
            }

            document.removeEventListener('touchmove', this.onDragMove);
            document.removeEventListener('mousemove', this.onDragMove);

            this.$emit('dragend', this.localValue, e);
        },

        getNearestSnapPoint() {
            const previousSnapPoint = Math.floor(this.value / this.step) * this.step;
            const nextSnapPoint = previousSnapPoint + this.step;
            const midpoint = (previousSnapPoint + nextSnapPoint) / 2;

            return this.value >= midpoint ? nextSnapPoint : previousSnapPoint;
        },

        getEdge(a, b, c) {
            if (a < b) {
                return b;
            }

            if (a > c) {
                return c;
            }

            return a;
        }
    },

    components: {
        UiIcon
    },

    mixins: [
        RespondsToWindowResize
    ]
};
</script>

<style lang="scss">
@import './styles/imports';

$ui-slider-height                   : rem-calc(18px) !default;

// Track line
$ui-slider-track-height             : rem-calc(3px) !default;
$ui-slider-track-fill-color         : $brand-primary-color !default;
$ui-slider-track-background-color   : rgba(black, 0.12) !default;

// Drag thumb
$ui-track-thumb-size                : rem-calc(14px) !default;
$ui-track-thumb-fill-color          : $brand-primary-color !default;

// Focus ring
$ui-track-focus-ring-size                   : rem-calc(36px) !default;
$ui-track-focus-ring-transition-duration    : 0.2s !default;
$ui-track-focus-ring-color                  : rgba($ui-track-thumb-fill-color, 0.38) !default;

// Marker
$ui-slider-marker-size                      : rem-calc(36px);

.ui-slider {
    align-items: center;
    display: flex;
    outline: none;

    &:not(.is-disabled).is-active,
    &:not(.is-disabled).is-dragging {
        &:not(.has-marker) {
            .ui-slider__thumb::before {
                transform: scale(1);
            }
        }

        .ui-slider__marker {
            opacity: 1;
            transform: scale(1) translateY(rem-calc(-26px));
        }

        .ui-slider__marker-text {
            color: white;
        }

        .ui-slider__snap-point {
            opacity: 1;
        }

        .ui-slider__icon .ui-icon {
            color: $ui-track-thumb-fill-color;
        }
    }

    &.is-disabled {
        .ui-slider__icon {
            opacity: 0.6;
        }

        .ui-slider__track {
            cursor: default;
        }

        .ui-slider__track-fill {
            background-color: transparent;
        }

        .ui-slider__thumb {
            background-color: #DDD;
            border: rem-calc(2px) solid white;
        }
    }
}

.ui-slider__icon {
    margin-right: rem-calc(16px);

    .ui-icon {
        color: $secondary-text-color;
        transition: color $ui-track-focus-ring-transition-duration ease;
    }
}

.ui-slider__track {
    align-items: center;
    cursor: pointer;
    display: flex;
    height: $ui-slider-height;
    margin: 0 auto;
    position: relative;
    width: 100%;
}

.ui-slider__track-background,
.ui-slider__track-fill {
    content: '';
    display: block;
    height: $ui-slider-track-height;
    left: 0;
    position: absolute;
    top: ($ui-slider-height - $ui-slider-track-height) / 2;
}

.ui-slider__track-background {
    background-color: $ui-slider-track-background-color;
    width: 100%;
}

.ui-slider__snap-point {
    background-color: rgba(black, 0.75);
    height: $ui-slider-track-height;
    opacity: 0;
    position: absolute;
    transition: opacity 0.2s ease;
    width: rem-calc(2px);
    z-index: 1;
}

.ui-slider__track-fill {
    background-color: $ui-slider-track-fill-color;
    transform-origin: left;
    width: 100%;
}

.ui-slider__thumb {
    background-color: $ui-track-thumb-fill-color;
    border-radius: 50%;
    cursor: inherit;
    display: block;
    height: $ui-track-thumb-size;
    left: 0;
    position: relative;
    width: $ui-track-thumb-size;
    z-index: 1;

    // Focus ring
    &::before {
        background-color: $ui-track-focus-ring-color;
        border-radius: 50%;
        content: '';
        display: block;
        height: $ui-track-focus-ring-size;
        margin-left: -($ui-track-focus-ring-size - $ui-track-thumb-size) / 2;
        margin-top: -($ui-track-focus-ring-size - $ui-track-thumb-size) / 2;
        position: absolute;
        transform-origin: center;
        transform: scale(0);
        transition: transform $ui-track-focus-ring-transition-duration ease;
        width: $ui-track-focus-ring-size;
    }
}

.ui-slider__marker {
    height: $ui-slider-marker-size;
    margin-left: -($ui-slider-marker-size - $ui-track-thumb-size) / 2;
    margin-top: -($ui-slider-marker-size - $ui-track-thumb-size) / 2;
    opacity: 0;
    position: absolute;
    transform: scale(0) translateY(0) ;
    transition: all $ui-track-focus-ring-transition-duration ease;
    user-select: none;
    width: $ui-slider-marker-size;

    svg {
        fill: $ui-track-thumb-fill-color;
        height: $ui-slider-marker-size;
        width: $ui-slider-marker-size;
    }
}

.ui-slider__marker-text {
    color: $ui-track-thumb-fill-color;;
    font-size: rem-calc(13px);
    font-weight: 500;
    left: 0;
    position: absolute;
    text-align: center;
    top: rem-calc(4px);
    transition: color $ui-track-focus-ring-transition-duration ease;
    width: $ui-slider-marker-size;
}

// Applied globally to the body tag when dragging
.ui-slider--is-dragging {
    user-select: none;
}
</style>
